<?php
/*
 * Copyright (c) 2013 Eugene Kuzmenko
 *
 * Permission  is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in  the Software without restriction, including without limitation the rights
 * to  use,  copy,  modify,  merge, publish, distribute, sublicense, and/or sell
 * copies  of  the  Software,  and  to  permit  persons  to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The  above  copyright  notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE  SOFTWARE  IS  PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED,  INCLUDING  BUT  NOT  LIMITED  TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS  FOR  A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS  OR  COPYRIGHT  HOLDERS  BE  LIABLE  FOR  ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * For more information, see <http://www.aljey.com>.
 */

namespace Al\Style\Lib\Form\Element;

use Al\Core\Lib\Service,
    Al\Style\Lib\Form\Field,
    Doctrine\Common\Util\Inflector,
    Al\Doctrine\Lib\Em;

/**
 * Allows the selection of one or more options. Rendered as a combo box, radio
 * buttons, or checkboxes.
 *
 * @property array $values A collection of option values.
 * @property array $output A collection of option labels.
 * @property array $options A key => value map of option values and labels.
 * @property array $value A collection of values that are currently selected.
 * @property bool $multiple True to allow the selection of multiple options at
 *                          once.
 * @property bool $expanded True to render the editor as a set of checkboxes or
 *                          radio buttons instead of a single combo box.
 * @property bool $required True to make the field required.
 * @property bool $disabled True to make the field disabled.
 * @property string $placeholder A string of text do display in an empty editor.
 * @property string $name A form element name.
 * @property bool $inline True to make the field inline.
 * @property string $assoc A class name of an entity that can be used to
 *                         automatically populate the options of a choice
 *                         control.
 * @author Eugene Kuzmenko <mail@aljey.com>
 */
class Choice extends Element {

  /**
   * @var array A collection of input instances in an expanded component.
   */
  private $items = array();

  /**
   * @var string If set - either radio or checkbox in an expanded control.
   */
  private $box_type;

  /**
   * @param array $params Config array.
   * @param Field $field
   */
  public function __construct($params = array(), Field $field = null) {
    $this->field = $field ? $field : new Field;
    $this->configure(array(
      'values' => array(),
      'output' => array(),
      'options' => array(),
      'multiple' => false,
      'expanded' => false,
      'required' => true,
      'disabled' => false,
      'cls' => array('form-control'),
      'placeholder' => '',
      'name' => uniqid('name-'),
      'inline' => true,
    ), $params);
    $this->config['attr']['name'] = $this->name;
    if ($this->multiple) $this->config['attr']['name'] .= '[]';
    if ($this->disabled) $this->config['attr']['disabled'] = 'disabled';
    if (!$this->values && $this->output) $this->values = $this->output;
    if (!$this->output && $this->values) $this->output = $this->values;
    $this->options += array_combine($this->values, $this->output);
    parent::__construct(array(), $field);
  }

  /**
   * Instantiates an input element for the expanded control.
   *
   * @param string $value Option value.
   * @param string $name Option display text.
   * @param bool $required
   * @return Text
   */
  private function createItem($value, $name, $required) {
    /** @noinspection PhpWrongStringConcatenationInspection */
    $input = new Text(array(
      'required' => $required,
      'inputType' => $this->box_type,
      'value' => $value,
      'attr' => array('checked' => in_array($value, $this->value, true) ?
        'checked' : ''),
      'label' => Service::get('config')->trans($name),
      'help' => '',
    ) + $this->config);
    $input->config['attr']['class'] = '';
    return $input;
  }

  /**
   * Returns a string representation of a component.
   *
   * @return string
   */
  public function __toString() {
    if ($this->assoc) {
      foreach (Service::get("em.$this->assoc")->findAll() as $entity) {
        $name = Service::get('config.mapping.' . get_class($entity) . '.name',
          'name');
        $this->config['options'][$entity->getId()] = isset($entity->$name) ?
          $entity->$name : (is_callable(array($entity, $method = 'get' .
            Inflector::classify($name))) ? $entity->$method() : "$entity");
      }
    }
    if (!$this->value) $this->value = array();
    if (!is_array($this->value)) $this->value = array($this->value);
    if ($this->required) {
      $this->config['attr']['required'] = 'required';
      if (!count($this->value)) {
        $this->config['value'][] = key($this->config['options']);
      }
    }
    if ($this->expanded) {
      $num_options = count($this->options);
      if (!$num_options) {
        $num_options = 1;
        $this->config['options']['1'] = '&nbsp;';
      }
      $multi = 1 < $num_options;
      $this->box_type = (!$this->multiple && $multi) ? 'radio' : 'checkbox';
      foreach ($this->options as $key => $value) {
        $this->items[$key] = $this->createItem($key, $value,
          $this->required && $multi);
      }
      $tpl = 'checkbox';
      $params = array(
        'inline' => $this->inline,
        'type' => $this->box_type,
        'items' => $this->items,
      );
    } else {
      $this->tag = 'select';
      if ($this->multiple) $this->config['attr']['multiple'] = 'multiple';
      $tpl = 'options';
      $params = array(
        'placeholder' => $this->placeholder,
        'options' => $this->options,
        'value' => $this->value,
      );
    }
    $result = Service::get('template')->render(
      Service::get("config.plugins.Al\\Style.form.$tpl"), $params);
    $this->html = $result[0];
    return parent::__toString();
  }

  /** @inheritdoc */
  public function prePersist(Em $em, $entity) {
    if ($this->assoc) {
      if (($id = Service::get("http.post.$this->name")) &&
          ($rec = $em->getRepository($this->assoc)->find($id))) {
        $em->entitySet($entity, $this->name, $rec);
      }
    } else {
      $em->entitySet($entity, $this->name, Service::get("http.post.$this->name",
        '0'));
    }
  }

} 